<?php

namespace App\Remote;

use App\Exceptions\BaseException;
use App\Kuafu\Base\Enum\BaseCodeEnum;
use App\Library\FeiShu\SendFs;
use Closure;
use Exception;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\ClientException;
use GuzzleHttp\Exception\GuzzleException;
use GuzzleHttp\Handler\CurlHandler;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Middleware;
use GuzzleHttp\Psr7\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
use Symfony\Component\HttpFoundation\Request as RequestAlias;
use Symfony\Component\HttpFoundation\Response as ResponseAlias;

abstract class Remote
{

    /**
     * 设置请求超时时长
     *
     * @var int
     */
    public static int $requestTimeOut = 10;

    /**
     * http 请求异常抛出开关
     *
     * @var bool
     */
    public static bool $httpError = false;

    /**
     * 请求路径前缀
     *
     * @return string
     */
    abstract static function getBaseUri(): string;

    /**
     * 注册结果自定义处理方法
     *
     * @return Closure
     */
    public static function registerResponseHandle(): Closure
    {
        return function (string $data) {
            return json_decode($data, JSON_UNESCAPED_UNICODE);
        };
    }

    /**
     * @return string
     */
    public static function registerFsRobot(): string
    {
        return SendFs::ROBOT_FS_BUSINESS_ERROR;
    }

    /**
     * 设置基础头信息
     *
     * @return array
     */
    public static function buildBasicHeaders(): array
    {
        return [];
    }

    /**
     * @param string $uri
     * @param string $requestMethod
     * @param array $params
     * @param array $headers
     * @return mixed|void
     * @throws GuzzleException
     * @throws BaseException
     */
    public  static function customRequest(
        string $uri,
        string $requestMethod,
        array $params = [],
        array $headers = []
    ) {
        try {
            $client = new Client();

            $requestUri = static::getBaseUri() . $uri;
            $headers    = array_merge(static::buildBasicHeaders(), $headers);
            $body       = function () use ($requestMethod, $headers, $params) {
                switch (strtoupper($requestMethod)) {
                    case RequestAlias::METHOD_GET:
                        $body = null;
                        break;
                    case RequestAlias::METHOD_POST:
                        if (isset($headers["Content-Type"]) && $headers["Content-Type"] == "application/json") {
                            $body = json_encode($params, JSON_UNESCAPED_UNICODE);
                        } else {
                            $body = http_build_query($params);
                        }
                        break;
                }
                return $body;
            };

            $request = new Request(
                $requestMethod,
                strtoupper($requestMethod) == RequestAlias::METHOD_GET ? $requestUri . "?" . http_build_query($params) : $requestUri,
                $headers,
                $body()
            );

            $response = $client->send($request, [
                "http_errors" => self::$httpError,
                "timeout"     => self::$requestTimeOut
            ]);

            $responseHttpCode = $response->getStatusCode();
            $responseContent  = $response->getBody()->getContents();
            $logContent       = self::getLogContent(
                $requestUri, $requestMethod, $headers, $params, $responseHttpCode, $responseContent
            );
            Log::channel("remoteRequest")->info("[ " . class_basename(static::class) . " ] 远程调用", $logContent);

            if ($response->getStatusCode() == ResponseAlias::HTTP_OK) {
                return (static::registerResponseHandle())($responseContent);
            }
            throw new Exception($logContent, BaseCodeEnum::ERR_REMOTE_REQUEST);

        } catch (Exception $exception) {
            if (app()->runningInConsole()) {
                // 运行在命令行下
                throw $exception;
            } else {
                handleException($exception, "获取远程数据失败", BaseCodeEnum::ERR_REMOTE_REQUEST);
            }
        }

    }

    /**
     * @param string $uri
     * @param string $requestMethod
     * @param array|null $params
     * @param array|null $headers
     * @param int $cacheTime
     * @param string|null $cacheKey
     * @return mixed|void
     * @throws BaseException
     * @throws GuzzleException
     */
    public static function customCacheRequest(
        string $uri,
        string $requestMethod,
        array $params = null,
        array $headers = null,
        int $cacheTime = 0,
        string $cacheKey = null
    ) {
        $requestClosure = function () use ($uri, $requestMethod, $params, $headers) {
            return self::customRequest($uri, $requestMethod, $params ?? [], $headers ?? []);
        };
        if ($cacheTime > 0 && strtoupper($requestMethod) == RequestAlias::METHOD_GET) {
            $args     = func_get_args();
            $args[3]  = array_merge(static::buildBasicHeaders(), $args[3] ?? []);
            $cacheKey = $cacheKey ?? sprintf(
                    "REMOTE:%s",
                    strtoupper(md5(json_encode($args)))
                );

            return Cache::store("redis")->remember(
                $cacheKey,
                $cacheTime,
                $requestClosure
            );
        } else {
            return $requestClosure();
        }
    }

    /**
     * @param string $requestUri
     * @param string $requestMethod
     * @param array $headers
     * @param array $requestParams
     * @param int $responseHttpCode
     * @param string $responseContent
     * @return array
     */
    public static function getLogContent(
        string $requestUri,
        string $requestMethod,
        array $headers,
        array $requestParams,
        int $responseHttpCode,
        string $responseContent
    ): array {
        return collect([
            "time"     => formatDate(time()),
            "httpCode" => $responseHttpCode,
            "request"  => [
                "uri"           => $requestUri,
                "headers"       => $headers,
                "requestMethod" => $requestMethod,
                "requestParams" => $requestParams
            ],
            "response" => [
                "content" => $responseHttpCode == ResponseAlias::HTTP_OK || isJsonStr($responseContent) ? $responseContent : ""
            ]
        ])->toArray();
    }


    /**
     * @param string $uri
     * @param string $requestMethod
     * @param array|string|null $params
     * @param array|null $headers
     * @param Closure|null $handleResponseClosure
     * @param int $cacheTime
     * @param bool $throwException
     * @return mixed
     * @throws GuzzleException|BaseException
     */
    public static function customRequestV2(
        string $uri,
        string $requestMethod,
        $params = null,
        array $headers = null,
        Closure $handleResponseClosure = null,
        int $cacheTime = 0,
        bool $throwException = false
    ) {
        try {
            $requestFunc = function () use ($uri, $requestMethod, $params, $headers, $handleResponseClosure) {
                $client = new Client(['verify' => false]);
                //请求地址
                $requestUri = Str::startsWith($uri, ["http", "https"]) ? $uri : vsprintf(
                    "%s/%s",
                    [trim(static::getBaseUri(), "/"), trim($uri, "/")]
                );

                //请求头信息
                $requestHeaders = array_filter(array_merge(
                    array_change_key_case(static::buildBasicHeaders(), CASE_LOWER),
                    array_change_key_case($headers ?? [], CASE_LOWER)
                ));

                $body    = null;
                $options = [
                    "http_errors" => static::$httpError,
                    "timeout"     => static::$requestTimeOut
                ];

                if (isset($requestHeaders["content-type"]) && !empty($params)) {
                    switch (strtolower($requestHeaders["content-type"])) {
                        case "application/json":
                            $body = json_encode($params);
                            break;
                        case "application/x-www-form-urlencoded":
                            $body = http_build_query($params);
                            break;
                        case "multipart/form-data":
                            $options        = array_merge($options, $params);
                            $requestHeaders = [];
                            $body           = null;
                            break;
                        case "application/xml":
                            $body = $params;
                            break;
                    }
                }

                $request = new Request(
                    $requestMethod,
                    $requestUri,
                    $requestHeaders,
                    $body
                );


                $requestOriginalContents  = $request->getBody()->getContents();
                $response                 = $client->send($request, $options);
                $responseOriginalContents = $response->getBody()->getContents();
                $responseHttpCode         = $response->getStatusCode();

                Log::channel("remoteRequest")->info("[ " . class_basename(static::class) . " ] 远程调用", [
                    "request"  => [
                        "requestUrl"      => buildParsedUrl($request->getUri()),
                        "requestMethod"   => $request->getMethod(),
                        "requestHeaders"  => $request->getHeaders(),
                        "requestContents" => remoteContentsParser($requestOriginalContents, $requestOriginalContents)
                    ],
                    "response" => [
                        "responseHttpCode" => $responseHttpCode,
                        "responseContents" => remoteContentsParser($responseOriginalContents, $responseOriginalContents)
                    ]
                ]);

                if ($handleResponseClosure instanceof Closure) {
                    return $handleResponseClosure($responseHttpCode, $responseOriginalContents);
                }
                return (static::registerResponseHandle())($responseHttpCode, $responseOriginalContents);
            };

            if ($cacheTime > 0) {
                $args     = func_get_args();
                $args[3]  = array_merge(static::buildBasicHeaders(), $args[3] ?? []);
                $cacheKey = $cacheKey ?? sprintf(
                        "KF:OS:REMOTE:%s",
                        strtoupper(md5(json_encode($args)))
                    );

                return Cache::remember(
                    $cacheKey,
                    $cacheTime,
                    $requestFunc
                );
            } else {
                return $requestFunc();
            }
        } catch (Exception $exception) {
            if (!$throwException) {
                handleException(
                    $exception,
                    " 远程调用失败 [ " . class_basename(static::class) . " ] [ {$exception->getMessage()} ]",
                    BaseCodeEnum::ERR_REMOTE_REQUEST,
                    null,
                    static::registerFsRobot()
                );
            }
            throw $exception;
        }
    }

    /**
     * @return HandlerStack
     */
    public static function getHandlerStack(): HandlerStack
    {
        $handleStack = HandlerStack::create(new CurlHandler());
        //基础日志中间件
        $handleStack->push(
            Middleware::log(Log::channel("remoteRequest"), new RemoteMessageFormatter(static::class)),
            "logMiddleware"
        );
        return $handleStack;
    }


    /**
     * @param string $uri
     * @param string $requestMethod
     * @param array|string|null $params
     * @param array|null $headers
     * @param Closure|null $handleResponseClosure
     * @param int $cacheTime
     * @param bool $throwException
     * @return mixed
     * @throws GuzzleException|BaseException
     */
    public static function customRequestV3(
        string $uri,
        string $requestMethod,
        $params = null,
        array $headers = null,
        Closure $handleResponseClosure = null,
        int $cacheTime = 0,
        bool $throwException = false
    ) {
        try {
            $requestFunc = function () use ($uri, $requestMethod, $params, $headers, $handleResponseClosure) {

                $client = new Client(['verify' => false, "handler" => static::getHandlerStack()]);
                //请求地址
                $requestUri = Str::startsWith($uri, ["http", "https"]) ? $uri : vsprintf(
                    "%s/%s",
                    [trim(static::getBaseUri(), "/"), trim($uri, "/")]
                );

                //请求头信息
                $requestHeaders = array_filter(array_merge(
                    array_change_key_case(static::buildBasicHeaders(), CASE_LOWER),
                    array_change_key_case($headers ?? [], CASE_LOWER)
                ));

                $body    = null;
                $options = [
                    "http_errors" => static::$httpError,
                    "timeout"     => static::$requestTimeOut
                ];

                if (isset($requestHeaders["content-type"]) && !empty($params)) {
                    switch (strtolower($requestHeaders["content-type"])) {
                        case "application/json":
                            $body = json_encode($params);
                            break;
                        case "application/x-www-form-urlencoded":
                            $body = http_build_query($params);
                            break;
                        case "multipart/form-data":
                            $options        = array_merge($options, $params);
                            $requestHeaders = [];
                            $body           = null;
                            break;
                        case "application/xml":
                            $body = $params;
                            break;
                    }
                }

                $request = new Request(
                    $requestMethod,
                    $requestUri,
                    $requestHeaders,
                    $body
                );

                $response                 = $client->send($request, $options);
                $responseOriginalContents = $response->getBody()->getContents();
                $responseHttpCode         = $response->getStatusCode();

                if ($handleResponseClosure instanceof Closure) {
                    return $handleResponseClosure($responseHttpCode, $responseOriginalContents);
                }
                return (static::registerResponseHandle())($responseHttpCode, $responseOriginalContents);
            };

            if ($cacheTime > 0) {
                $args     = func_get_args();
                $args[3]  = array_merge(static::buildBasicHeaders(), $args[3] ?? []);
                $cacheKey = $cacheKey ?? sprintf(
                        "KF:OS:REMOTE:%s",
                        strtoupper(md5(json_encode($args)))
                    );

                return Cache::remember(
                    $cacheKey,
                    $cacheTime,
                    $requestFunc
                );
            } else {
                return $requestFunc();
            }
        } catch (Exception $exception) {
            if (!$throwException) {
                handleException(
                    $exception,
                    " 远程调用失败 [ " . class_basename(static::class) . " ] [ {$exception->getMessage()} ]",
                    BaseCodeEnum::ERR_REMOTE_REQUEST,
                    null,
                    static::registerFsRobot()
                );
            }
            throw $exception;
        }
    }
}
